require 'JSON'

module Cms
  class Fragment < Widget

    def options
      p(get_state)
      get_state.to_json
    end
    
    def self.compose json_or_hash
      if json_or_hash.is_a? String
        state = JSON.parse(json_or_hash)
      else
        state = json_or_hash
      end
      
      fragment_state = state[0]
      fragment = Magic.to_const(fragment_state['type'])
      if fragment.ancestors.include? Fragment
        #p fragment.to_s+' is a fragment'
        f = fragment.new
        f.set_state(state)
      else
        raise(fragment.to_s+' is NOT a fragment')
      end
      #p "state is #{state}"
      f
    end
    
    def set_children_states states
      #p ['set_children_states', states]
      states.each do |state|
        #p 'compose '+state.inspect
        add Fragment.compose(state)
      end
    end

    def self.state_defined_by map
      raise 'Give me hash.' unless map.is_a?(Hash)
      @@_fragment_state_defined_by ||= {}
      @@_fragment_state_defined_by[self] = map.keys.collect { |k| k.to_sym }
      map.each_pair do |attr, default_value|
        class_eval "def #{attr}= val
          state[:#{attr}] = val
        end"
        class_eval "def #{attr}
          if state.has_key? :#{attr}
            state[:#{attr}]
          else
            #{default_value.inspect}
          end
        end"
      end
    end

    state_defined_by :draggable => true

    def state_defined_by
      ([self.class] + self.class.ancestors).collect { |cls|
        @@_fragment_state_defined_by[cls]
      }.flatten.compact
    end
    
    attr_accessor :state

    def set_state state
      begin
        my_state = Hash[*state[0].collect { |k, v| [k.to_sym, v] }.flatten]
        #p self.class.to_s+' set state'+state.inspect
        set_children_states(state[1])
        @state = my_state
      rescue
        f = add TextFragment.new()
        puts f.data = $!.to_s + "\n" + $!.backtrace.join("\n")
      end
    end

    def state
      @state ||= {}
    end

    def get_state
      (Hash[*state_defined_by.collect { |attr|
           [attr, self.send(attr)]
         }.flatten
       ]).merge( 'type' => self.class.to_s )
    end

    def serialize
      p d = decompose
      d.to_json
    end

    def decompose
      [get_state, children.collect { |ch|
          if ch.respond_to? 'decompose'
            ch.decompose
          end
        }.compact]
    end

    def self.unserialize data
      compose(data)
    end

  end
end